﻿namespace PugLib.UnitTest
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Linq;

	using Microsoft.VisualStudio.TestTools.UnitTesting;

	using PugLib.Collections;

	/// <summary>
	/// Unit tests against Memoize.
	/// </summary>
	[TestClass]
	public class MemoizeTest
	{
		private readonly IDictionary<int, long> _factorialLookup = new Dictionary<int, long>
		                                                           {
		                                                           	{-10, -3628800},
		                                                           	{-9, -362880},
		                                                           	{-8, -40320},
		                                                           	{-7, -5040},
		                                                           	{-6, -720},
		                                                           	{-5, -120},
		                                                           	{-4, -24},
		                                                           	{-3, -6},
		                                                           	{-2, -2},
		                                                           	{-1, -1},
		                                                           	{0, 1},
		                                                           	{1, 1},
		                                                           	{2, 2},
		                                                           	{3, 6},
		                                                           	{4, 24},
		                                                           	{5, 120},
		                                                           	{6, 720},
		                                                           	{7, 5040},
		                                                           	{8, 40320},
		                                                           	{9, 362880},
		                                                           	{10, 3628800}
		                                                           };

		private readonly decimal[] _fibLookup = new[] {1m, 1m, 2m, 3m, 5m, 8m, 13m, 21m, 34m, 55m, 89m, 144m, 233m, 377m, 610m, 987m, 1597m, 2584m, 4181m, 6765m, 10946m, 17711m, 28657m, 46368m, 75025m, 121393m, 196418m, 317811m, 514229m, 832040m, 1346269m, 2178309m, 3524578m, 5702887m, 9227465m, 14930352m, 24157817m, 39088169m, 63245986m, 102334155m, 165580141m, 267914296m, 433494437m, 701408733m, 1134903170m, 1836311903m, 2971215073m, 4807526976m, 7778742049m, 12586269025m, 20365011074m, 32951280099m, 53316291173m, 86267571272m, 139583862445m, 225851433717m, 365435296162m, 591286729879m, 956722026041m, 1548008755920m, 2504730781961m, 4052739537881m, 6557470319842m, 10610209857723m, 17167680177565m, 27777890035288m, 44945570212853m, 72723460248141m, 117669030460994m, 190392490709135m, 308061521170129m, 498454011879264m, 806515533049393m, 1304969544928657m, 2111485077978050m, 3416454622906707m, 5527939700884757m, 8944394323791464m, 14472334024676221m, 23416728348467685m, 37889062373143906m, 61305790721611591m, 99194853094755497m, 160500643816367088m, 259695496911122585m, 420196140727489673m, 679891637638612258m, 1100087778366101931m, 1779979416004714189m, 2880067194370816120m, 4660046610375530309m, 7540113804746346429m, 12200160415121876738m, 19740274219868223167m, 31940434634990099905m, 51680708854858323072m, 83621143489848422977m, 135301852344706746049m, 218922995834555169026m, 354224848179261915075m};

		/// <summary>
		/// Test Memoize accessors with int type.
		/// </summary>
		[TestMethod]
		public void Int_IntegrityTest()
		{
			Func<int, int> func = x => x * 2;
			Func<int, int> memoizeFunc = Memos.Memoize(func);
			for (int i = 0; i < 100; i++)
			{
				Assert.AreEqual(func(i), memoizeFunc(i), "Function return value mismatch"); // cache miss
				Assert.AreEqual(func(i), memoizeFunc(i), "Function return value mismatch"); // cache hit
				Assert.AreEqual(func(i), memoizeFunc(i), "Function return value mismatch"); // cache hit
			}
		}

		/// <summary>
		/// Test Memoize accessors with a struct type.
		/// </summary>
		[TestMethod]
		public void Struct_IntegrityTest()
		{
			Func<MyStruct, double> func = x => (int)(x.Number * double.Parse(x.NumberString));
			Func<MyStruct, double> memoizeFunc = Memos.Memoize(func);

			for (int i = 0; i < 100; i++)
			{
				double v = func(new MyStruct {Key = i, Number = i * 1.5, NumberString = (i * Math.PI).ToString()});
				Assert.AreEqual(v, memoizeFunc(new MyStruct {Key = i, Number = i * 1.5, NumberString = (i * Math.PI).ToString()}), "Function return value mismatch"); // cache miss
				Assert.AreEqual(v, memoizeFunc(new MyStruct {Key = i, Number = i * 1.5, NumberString = (i * Math.PI).ToString()}), "Function return value mismatch"); // cache hit
				Assert.AreEqual(v, memoizeFunc(new MyStruct {Key = i, Number = i * 1.5, NumberString = (i * Math.PI).ToString()}), "Function return value mismatch"); // cache hit
			}
		}

		/// <summary>
		/// Test Memoize accessors with an object type.
		/// </summary>
		[TestMethod]
		public void Object_IntegrityTest()
		{
			Func<MyClass, double> func = x => (int)(x.Number * double.Parse(x.NumberString));
			Func<MyClass, double> memoizeFunc = Memos.Memoize(func);

			for (int i = 0; i < 100; i++)
			{
				double v = func(new MyClass {Key = i, Number = i * 1.5, NumberString = (i * Math.PI).ToString()});
				Assert.AreEqual(v, memoizeFunc(new MyClass {Key = i, Number = i * 1.5, NumberString = (i * Math.PI).ToString()}), "Function return value mismatch"); // cache miss
				Assert.AreEqual(v, memoizeFunc(new MyClass {Key = i, Number = i * 1.5, NumberString = (i * Math.PI).ToString()}), "Function return value mismatch"); // cache hit
				Assert.AreEqual(v, memoizeFunc(new MyClass {Key = i, Number = i * 1.5, NumberString = (i * Math.PI).ToString()}), "Function return value mismatch"); // cache hit
			}
		}

		/// <summary>
		/// Test Memoize accessors with recursive function.
		/// </summary>
		[TestMethod]
		public void Recursion_IntegrityTest()
		{
			Func<int, decimal> memoizeFunc = Memos.Memoize<int, decimal>((n, self) => n < 2 ? 1 : (self(n - 1) + self(n - 2)));

			for (int x = 0; x < _fibLookup.Length; x++)
			{
				decimal value = _fibLookup[x];

				for (int i = 0; i < 3; i++)
				{
					Assert.AreEqual(value, memoizeFunc(x), "Function value mismatch");
				}

				Debug.WriteLine(string.Format("Fibonacci {0} = {1}", x, value));
			}
		}

		/// <summary>
		/// Test cache efficiency.
		/// </summary>
		[TestMethod]
		public void EfficiencyTest()
		{
			int[] fibCounter = {0};

			// Optimized fibonacci memoization
			Func<int, int> memoizeFunc = null;
			memoizeFunc = ((int n) =>
			               {
			               	fibCounter[0]++;
			               	return n < 2 ? 1 : memoizeFunc(n - 1) + memoizeFunc(n - 2);
			               });
			memoizeFunc = Memos.Memoize(memoizeFunc);

			for (int i = 0; i < 100000; i++)
			{
				fibCounter[0] = 0;
				memoizeFunc(i);
				Assert.AreEqual(1, fibCounter[0], "Expected a single cache miss");

				fibCounter[0] = 0;
				memoizeFunc(i);
				Assert.AreEqual(0, fibCounter[0], "Expected a cache hit");

				fibCounter[0] = 0;
				memoizeFunc(i);
				Assert.AreEqual(0, fibCounter[0], "Expected a cache hit");
			}
		}

		/// <summary>
		/// Test cache efficiency with recursive function.
		/// </summary>
		[TestMethod]
		public void Recursive_EfficiencyTest()
		{
			int[] fibCounter = {0};

			// Simplification to CacheHit_Memoize() test
			Func<int, int> memoizeFunc = Memos.Memoize((int n, Func<int, int> self) =>
			                                           {
			                                           	fibCounter[0]++;
			                                           	return n < 2 ? 1 : self(n - 1) + self(n - 2);
			                                           });

			for (int i = 0; i < 100000; i++)
			{
				fibCounter[0] = 0;
				memoizeFunc(i);
				Assert.AreEqual(1, fibCounter[0], "Expected a single cache miss");

				fibCounter[0] = 0;
				memoizeFunc(i);
				Assert.AreEqual(0, fibCounter[0], "Expected a cache hit");

				fibCounter[0] = 0;
				memoizeFunc(i);
				Assert.AreEqual(0, fibCounter[0], "Expected a cache hit");
			}
		}

		/// <summary>
		/// Test efficiency when providing a prepopulated dictionary.
		/// </summary>
		[TestMethod]
		public void PrepopulatedCache_EfficiencyTest()
		{
			// Provide pre-populated cache, so the fibonacci function should never be called
			int idx = 0;
			Dictionary<int, decimal> cache = _fibLookup.ToDictionary(n => idx++);

			int[] fibCounter = {0};
			Func<int, decimal> memoizeFunc = Memos.Memoize((n, self) =>
			                                               {
			                                               	fibCounter[0]++;
			                                               	return n < 2 ? 1 : self(n - 1) + self(n - 2);
			                                               }, cache);

			for (int i = 0; i < _fibLookup.Length; i++)
			{
				fibCounter[0] = 0;
				memoizeFunc(i);
				Assert.AreEqual(0, fibCounter[0], "Expected a cache hit");

				fibCounter[0] = 0;
				memoizeFunc(i);
				Assert.AreEqual(0, fibCounter[0], "Expected a cache hit");

				fibCounter[0] = 0;
				memoizeFunc(i);
				Assert.AreEqual(0, fibCounter[0], "Expected a cache hit");
			}
		}

		// Precalculated foctorial lookup

		/// <summary>
		/// Test accessors with a factorial function.
		/// </summary>
		[TestMethod]
		public void Factorial_IntegrityTest()
		{
			Func<int, long> factorial = Memos.Memoize<int, long>((x, self) => x > 1 || x <= -1 ? x * self(Math.Abs(x) - 1) : 1);

			foreach (KeyValuePair<int, long> pair in _factorialLookup.OrderBy(x => x.Key))
			{
				int x = pair.Key;
				long value = pair.Value;

				for (int i = 0; i < 3; i++)
				{
					Assert.AreEqual(value, factorial(x), "Function value mismatch");
				}

				Debug.WriteLine(string.Format("{0}! = {1}", x, value));
			}
		}

		#region Nested type: MyClass
		private class MyClass
		{
			public int Key { private get; set; }
			public double Number { get; set; }
			public string NumberString { get; set; }

			// Compare class hashcodes by the value of its members
			public override int GetHashCode()
			{
				return Key.GetHashCode() + Number.GetHashCode() + NumberString.GetHashCode();
			}
		}
		#endregion

		#region Nested type: MyStruct
		private struct MyStruct
		{
			public int Key;
			public double Number;
			public string NumberString;

			// Compare struct hashcodes by the value of its members; necessary for Assert.AreEqual()
			public override int GetHashCode()
			{
				return Key.GetHashCode() + Number.GetHashCode() + NumberString.GetHashCode();
			}
		} ;
		#endregion
	}
}